package com.cskaoyan.javase.object._3equals;

/**
 * equals方法的声明，比较简单：
 * public boolean equals(Object obj)
 * 仅需要注意两点：
 *      1.该方法是有参数的，需要传入一个对象（任意一个对象就行）
 *      2.方法是有返回值的，返回一个布尔类型的值，真或假。
 *
 * 方法的作用:
 *      equals这个单词有"相等,相同"的意思
 *      该方法的调用需要一个对象,并且方法还会传入一个对象
 *      所以该方法的作用是,比较当前调用方法的对象和方法传入的对象是否相等
 *      如果相等就返回true,如果不相等就返回false
 *
 * 对象相等,如何去判断呢?
 * 需要一个比较对象的相等的规则,而这个规则就是equals方法的实现
 * equals方法的实现如何定义的,就是比较相等的规则如何定义的
 *
 * Object类当中的equals方法的默认实现(Object类默认的对象相等的规则)
 * "return (this == obj);"
 * 这个比较规则,直接用"=="连接方法调用者对象以及方法传入的对象
 * 这个比较的规则是非常苛刻的,说白了,只有自己才和自己相等
 * 在实际开发中,绝大多数情况下,我们不需要这么苛刻的比较规则
 *
 * 在对象与类的章节中,我们认为的对象相等是这样的:
 *      1.如果两个对象是不同种类型的对象,那么没有可比性,直接认定对象不相等
 *      2.如果两个对象是同种类型对象,那么就可以比较一下了:
 *          比较它们对象中属性的取值,如果属性的取值是一样的,那么就可以认定对象相等
 *
 * 如果希望自定义equals方法比较规则,希望在子类中重写equals方法
 * 那么遵循重写equals方法的常规协定,来重写该方法:
 *      1.自反性：对于任何非空引用值 x，x.equals(x)都应返回 true
 *      自己和自己比肯定还要返回true
 *
 *      2.排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
 *
 *      3.对称性：
 *      对于任何非空引用值 x 和 y，当且仅当y.equals(x) 返回 true 时，x.equals(y) 才应返回true
 *      x = y也意味着 y = x
 *
 *      4.传递性：
 *      对于任何非空引用值 x、y 和 z，如果x.equals(y)返回 true
 *      并且 y.equals(z) 返回 true，那么x.equals(z) 应返回 true。
 *      x = y ,y = z --> x = z
 *
 *      5.一致性：
 *      对于任何非空引用值 x 和 y，多次调用 x.equals(y)始终返回 true 或始终返回 false。
 *      前提是用于比较相等的数据没有发生变化
 *
 *      以上传递性,一致性,对称性,只需要按照对象的属性取值判断对象相等
 *      就可以自动满足了
 *
 * 以上就是equals方法基本使用了,我们一般按照对象的属性取值来判断对象相等.
 * equals方法使用的细节问题:
 *      1.排它性的实现,两种方式:
 *          a.getClass,很严格,只接受相同类型的对象传入方法,才有可能返回true
 *          b.instanceof,相对宽松,可以接受子类类型的对象传入方法,也有可能返回true
 *      2.实际开发中,如无特殊需求,建议自动生成equals方法,不要手写.
 *          快捷键 alt + insert
 *      3.实际开发中,比较浮点数的大小,建议使用:
 *          Float.compare(float a, float b)
 *          Double.compare(double a,double b)
 *          为什么不能用"=="?
 *          首先绝大多数场景,双等号是没问题的,双等号是比较数值的
 *          但是浮点数有特殊值,比如非数NaN,无穷大,无穷小等概念
 *          它们用"=="比较是不行的
 *          所以从代码的严谨出发,用以上方法判断浮点数大小是更好的选择.
 *          以上方法是如何判断大小呢?
 *          把上述方法,看成"a-b"
 *              如果a>b,方法返回大于0的数
 *              如果a<b,方法返回小于0的数
 *              如果a=b,方法返回0
 *
 *      4.成员变量如果是引用数据类型如何比较对象相等呢?
 *          只需要再去重写该引用数据类型成员变量的equals方法即可
 *
 *
 * @since 11:21
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 600);
        Student s2 = new Student(18, 600);
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
        Student s3 = s1;
        System.out.println(s1 == s3);
        System.out.println(s1.equals(s3));

        float f1 = Float.NaN;
        float f2 = Float.NaN;
        System.out.println(f1 == f2);

        System.out.println(Float.compare(f1, f2));
    }
}

class Student {
    int age;
    double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    // 手动重写equals方法,遵循重写的常规协定
    @Override
    public boolean equals(Object obj) {
        // 自反性: 先判断自身对象和传入的对象,是否是同一个对象,如果是,直接返回true
        /*
            "=="连接两个引用数据类型的引用
            能够通过编译,必须:
                a.两个引用的数据类型完全一致
                b.两个引用的数据类型必须具有继承的父子关系,在一条继承链中
         */
        if (this == obj) {
            return true;
        }

        // obj方法的传参,传入的完全可能不是一个对象,而是null
        // 排它性,排除null和非同种类型对象
        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }

        // 代码运行到这里,说明obj指向的对象,肯定不是this自身对象,是一个独立的新对象,并且类型一定是Student
        // 传递性,一致性,对称性,只需要按照对象的属性取值判断对象相等就能够自动满足
        Student target = (Student) obj;
        if (target.age != this.age){
            // age不等,对象就不等
            return false;
        }
        // 代码运行到这里,age是一定相等的,所以score的比较结果就直接决定了对象是否相等
        return target.score == this.score;
    }
}